package archer.back.controllers

import java.net.URLEncoder

import archer.back.models.CommonBody
import archer.back.utils.PoiStyles
import utils.{ ArcherHelper, CommonDB }
import io.circe.syntax._
import models.{ CusPoiOperations, UrlWrap }

import scala.concurrent.{ ExecutionContext, Future }
import cats.implicits._
import io.circe.Json
import org.xarcher.emiya.utils.{ EmbeddedServer, FutureLimitedGen }
import play.api.libs.ws.WSClient
import com.sksamuel.elastic4s.http.ElasticDsl._
import com.sksamuel.elastic4s.circe._
import com.sksamuel.elastic4s.http.search.SearchResponse
import net.scalax.cpoi.api._

import scala.util.{ Failure, Success, Try }

class SearchService(
  commonDB: CommonDB,
  wsClient: WSClient,
  futureLimitedGen: FutureLimitedGen,
  embeddedServer: EmbeddedServer)(implicit ec: ExecutionContext)
  extends ArcherHelper {

  def list(
    actionId: Int,
    include: String,
    exclude: String,
    hostInclude: String,
    hostExclude: String,
    refererInclude: String,
    refererExclude: String,
    protocolInclude: String,
    protocolExclude: String,
    urlInclude: String,
    urlExclude: String,
    typeInclude: String,
    typeExclude: String,
    minSize: String,
    planId: Int,
    page: Int,
    limit: Int): V.ResultT[SearchResponse] = {

    etLess.lift {

      val planIdFilter = List(termQuery("planId", planId))

      val autalSize = Try { minSize.toDouble }.getOrElse(-1d)
      val sizeFilter =
        if (autalSize >= 0d)
          List(rangeQuery("contentLength").gte((autalSize * 1024).toInt))
        else
          List.empty

      val actionFilter = List(termQuery("actionId", actionId))

      val hostExcludeTrim =
        hostExclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val hostMustNot = hostExcludeTrim.map {
        case hostName if ((hostName.indexOf('*') >= 0) || (hostName.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("host" -> hostName)))
          List(wildcardQuery("host", hostName))
        case hostName =>
          //List(Map("term" -> Map("host" -> hostName)))
          List(termQuery("host", hostName))
      }.flatten

      val hostIncludeTrim =
        hostInclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val hostMust = hostIncludeTrim.map {
        case hostName if ((hostName.indexOf('*') >= 0) || (hostName.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("host" -> hostName)))
          List(wildcardQuery("host", hostName))
        case hostName =>
          //List(Map("term" -> Map("host" -> hostName)))
          List(termQuery("host", hostName))
      }.flatten

      val protocolIncludeTrim =
        protocolInclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val protocolMust = protocolIncludeTrim.map {
        case protocol if ((protocol.indexOf('*') >= 0) || (protocol.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("protocol" -> protocol)))
          List(wildcardQuery("protocol", protocol))
        case protocol =>
          //List(Map("term" -> Map("protocol" -> protocol)))
          List(termQuery("protocol", protocol))
      }.flatten

      val protocolExcludeTrim =
        protocolExclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val protocolMustNot = protocolExcludeTrim.map {
        case protocol if ((protocol.indexOf('*') >= 0) || (protocol.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("protocol" -> protocol)))
          List(wildcardQuery("protocol", protocol))
        case protocol =>
          //List(Map("term" -> Map("protocol" -> protocol)))
          List(termQuery("protocol", protocol))
      }.flatten

      val codeExcludeTrim = Try {
        exclude.trim
          .split(',')
          .toList
          .map(_.trim)
          .filterNot(_.isEmpty)
          .map(_.toInt)
      }.toOption.toList.flatten
      val codeMustNot = if (codeExcludeTrim.isEmpty) {
        List.empty
      } else {
        //List(Map("terms" -> status("status" -> codeExcludeTrim.asJson)))
        List(termsQuery("status", codeExcludeTrim))
      }

      val codeIncludeTrim = Try {
        include.trim
          .split(',')
          .toList
          .map(_.trim)
          .filterNot(_.isEmpty)
          .map(_.toInt)
      }.toOption.toList.flatten
      val codeMust = if (codeIncludeTrim.isEmpty) {
        List.empty
      } else {
        //List(Map("terms" -> Map("status" -> codeIncludeTrim.asJson)))
        List(termsQuery("status", codeIncludeTrim))
      }

      val urlIncludeTrim =
        urlInclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val urlMust = urlIncludeTrim.map {
        case url if ((url.indexOf('*') >= 0) || (url.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("url" -> url)))
          List(wildcardQuery("url", url))
        case url =>
          //List(Map("term" -> Map("url" -> url)))
          List(termQuery("url", url))
      }.flatten

      val urlExcludeTrim =
        urlExclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val urlMustNot = urlExcludeTrim.map {
        case url if ((url.indexOf('*') >= 0) || (url.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("url" -> url)))
          List(wildcardQuery("url", url))
        case url =>
          //List(Map("term" -> Map("url" -> url)))
          List(termQuery("url", url))
      }.flatten

      val typeIncludeTrim =
        typeInclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val typeMust = typeIncludeTrim.map {
        case contentType if ((contentType.indexOf('*') >= 0) || (contentType.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("contentType" -> contentType)))
          List(wildcardQuery("contentType", contentType))
        case contentType =>
          //List(Map("term" -> Map("contentType" -> contentType)))
          List(termQuery("contentType", contentType))
      }.flatten

      val typeExcludeTrim =
        typeExclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val typeMustNot = typeExcludeTrim.map {
        case contentType if ((contentType.indexOf('*') >= 0) || (contentType.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("contentType" -> contentType)))
          List(wildcardQuery("contentType", contentType))
        case contentType =>
          //List(Map("term" -> Map("contentType" -> contentType)))
          List(termQuery("contentType", contentType))
      }.flatten

      val refererIncludeTrim =
        refererInclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val refererMust = refererIncludeTrim.map {
        case contentType if ((contentType.indexOf('*') >= 0) || (contentType.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("contentType" -> contentType)))
          List(wildcardQuery("referer", contentType))
        case contentType =>
          //List(Map("term" -> Map("contentType" -> contentType)))
          List(termQuery("referer", contentType))
      }.flatten

      val refererExcludeTrim =
        refererExclude.trim.split(',').toList.map(_.trim).filterNot(_.isEmpty)
      val refererMustNot = refererExcludeTrim.map {
        case contentType if ((contentType.indexOf('*') >= 0) || (contentType.indexOf('?') >= 0)) =>
          //List(Map("wildcard" -> Map("contentType" -> contentType)))
          List(wildcardQuery("referer", contentType))
        case contentType =>
          //List(Map("term" -> Map("contentType" -> contentType)))
          List(termQuery("referer", contentType))
      }.flatten

      val filters = actionFilter :::
        planIdFilter :::
        sizeFilter :::
        hostMust :::
        protocolMust :::
        codeMust :::
        urlMust :::
        typeMust :::
        refererMust
      val mustNots = hostMustNot :::
        protocolMustNot :::
        codeMustNot :::
        urlMustNot :::
        typeMustNot :::
        refererMustNot

      val jsonMod1 = boolQuery().filter(filters).not(mustNots)

      val searchAction = search(embeddedServer.index)
        .types(embeddedServer.typeName)
        .query(jsonMod1)
        .sourceExclude(
          List(
            "body",
            "cssBody",
            "textBody",
            "law_body",
            "law_text_body",
            "subs"))

      val searchAction2 = if ((limit > 0) && (page > 0)) {
        searchAction.size(limit).from(limit * (page - 1))
      } else {
        searchAction
      }

      val result = embeddedServer.esLocalClient
        .flatMap { client =>
          client.execute {
            searchAction2
          }
        }
        .map {
          case Right(r) =>
            r.result
          case Left(e) =>
            println(e)
            throw new Exception()
        }

      etLess.unlift(commonCatch(result))
    }
  }

  def toPoi(data: List[CommonBody], host: String): V.ResultT[(List[Map[String, CellDataAbs]], List[String])] = {
    object cusPoiOperations extends CusPoiOperations
    import cusPoiOperations._

    val crawlTime = "抓取时间"
    val url = "链接"
    val fixUrl = "溯源"
    val hostColumnName = "站点"
    val status = "状态码"
    val contentLength = "文件大小"

    etLess.lift {
      val dataMap = data.map { commonBodyWrap =>
        val fixedUrl =
          s"http://${host}/kunlunAssets/index.html#plan/trace/${commonBodyWrap.planId}?term=${
            URLEncoder
              .encode(commonBodyWrap.url, "utf-8")
          }&actionId=${commonBodyWrap.actionId}"

        val fixedUrlToShow =
          if (fixedUrl.size > 40) fixedUrl.take(36) + "..." else fixedUrl

        val commonUrlToShow =
          if (commonBodyWrap.url.size > 40) commonBodyWrap.url.take(36) + "..."
          else commonBodyWrap.url

        Map(
          crawlTime -> (CPoi
            .wrapData(commonBodyWrap.requestTime)
            .addTransform(PoiStyles.DateFormat("yyyy年MM月dd日HH时mm分ss秒"))
            .untyped),
          url -> (CPoi
            .wrapData(
              UrlWrap(showText = commonUrlToShow, url = commonBodyWrap.url))
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          fixUrl -> (CPoi
            .wrapData(UrlWrap(showText = fixedUrlToShow, url = fixedUrl))
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          hostColumnName -> (CPoi
            .wrapData(commonBodyWrap.host)
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          status -> (CPoi
            .wrapData(commonBodyWrap.status.toDouble)
            .addTransform(PoiStyles.BigDecimalFormat("0"))
            .untyped),
          contentLength -> (CPoi
            .wrapData((commonBodyWrap.contentLength / 1024d))
            .addTransform(PoiStyles.BigDecimalFormat("0.0KB"))
            .untyped))
      }
      dataMap -> List(
        hostColumnName,
        status,
        contentLength,
        crawlTime,
        url,
        fixUrl)
    }
  }

  val crawlTime = "抓取时间"
  val url = "链接"
  val fullUrl = "完整链接"
  val fixUrl = "溯源"
  val referer = "referer"
  val refererCount = "referer 数量"
  val hostColumnName = "站点"
  val status = "状态码"
  val contentLength = "文件大小"

  def toPoiWithReferer(data: List[CommonBody], host: String): V.ResultT[(List[Map[String, CellDataAbs]], List[String])] = {

    val futureLimited = futureLimitedGen.create(6, "es 请求 referer 限流逻辑")

    object cusPoiOperations extends CusPoiOperations
    import cusPoiOperations._

    etLess.lift {
      val dataMap = data.map { commonBodyWrap =>
        val fixedUrl =
          s"http://${host}/kunlunAssets/index.html#plan/trace/${commonBodyWrap.planId}?term=${
            URLEncoder
              .encode(commonBodyWrap.url, "utf-8")
          }&actionId=${commonBodyWrap.actionId}"

        val fixedUrlToShow =
          if (fixedUrl.size > 40) fixedUrl.take(36) + "..." else fixedUrl

        val commonUrlToShow =
          if (commonBodyWrap.url.size > 40) commonBodyWrap.url.take(36) + "..."
          else commonBodyWrap.url

        val mainMap = Map(
          crawlTime -> (CPoi
            .wrapData(commonBodyWrap.requestTime)
            .addTransform(PoiStyles.DateFormat("yyyy年MM月dd日HH时mm分ss秒"))
            .untyped),
          url -> (CPoi
            .wrapData(
              UrlWrap(showText = commonUrlToShow, url = commonBodyWrap.url))
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          fixUrl -> (CPoi
            .wrapData(UrlWrap(showText = fixedUrlToShow, url = fixedUrl))
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          hostColumnName -> (CPoi
            .wrapData(commonBodyWrap.host)
            .addTransform(PoiStyles.TextFormat())
            .untyped),
          status -> (CPoi
            .wrapData(commonBodyWrap.status.toDouble)
            .addTransform(PoiStyles.BigDecimalFormat("0"))
            .untyped),
          contentLength -> (CPoi
            .wrapData((commonBodyWrap.contentLength / 1024d))
            .addTransform(PoiStyles.BigDecimalFormat("0.0KB"))
            .untyped),
          fullUrl -> (CPoi
            .wrapData(commonBodyWrap.url)
            .addTransform(PoiStyles.TextFormat())
            .untyped))

        val queryAction = search(embeddedServer.index)
          .types(embeddedServer.typeName)
          .query(boolQuery().filter(
            termQuery("planId", commonBodyWrap.planId),
            termQuery("actionId", commonBodyWrap.actionId),
            nestedQuery(
              "subs",
              boolQuery().must(termQuery("subs.url", commonBodyWrap.url)))))
          .sourceExclude(
            List(
              "body",
              "cssBody",
              "textBody",
              "law_body",
              "law_text_body",
              "subs"))
          .size(30)

        embeddedServer.esLocalClient
          .flatMap(client =>
            futureLimited.limit(() => client.execute(queryAction), "es 限流逻辑"))
          .transform {
            case Success(Right(result)) =>
              Try {
                val rList = result.result.to[CommonBody]

                rList.map { each =>
                  val rMap = Map(
                    referer -> (CPoi
                      .wrapData(each.url)
                      .addTransform(PoiStyles.TextFormat())
                      .untyped),
                    refererCount -> (CPoi
                      .wrapData(result.result.totalHits.toDouble)
                      .untyped))

                  mainMap ++ rMap
                }
              }
            case Failure(e) =>
              Failure(e)
            case Success(Left(errResult)) =>
              Failure(new Exception(
                s"请求 referer 发生错误，错误原因为：${errResult.error.reason}，错误内容为：${errResult.body}"))
          }
      }

      val totalMap =
        etLess.unlift(commonCatch(Future.sequence(dataMap).map(_.flatten)))

      totalMap -> List(
        hostColumnName,
        status,
        contentLength,
        crawlTime,
        url,
        fixUrl,
        fullUrl,
        referer,
        refererCount)
    }
  }

}
